package helpers

import play.api.libs.json._

object ExtReads {

  def always[T](default: ⇒ T): Reads[T] = new Reads[T] {
    override def reads(json: JsValue) = JsSuccess(default)
  }

  implicit class ExtendJsPath(val path: JsPath) extends AnyVal {

    def formatNullable1[T](
        implicit fmt: Format[T]
    ): OFormat[Option[T]] = OFormat(
      Reads.nullable[T](path),
      Writes.at(path)(Writes.optionWithNull[T](fmt))
    )

    def formatNullable1[T](r: Reads[T])(
        implicit w: Writes[T]
    ): OFormat[Option[T]] = OFormat(
      Reads.nullable(path)(r),
      Writes.at(path)(Writes.optionWithNull[T](w))
    )

    def formatNullable1[T](w: Writes[T])(
        implicit r: Reads[T]
    ): OFormat[Option[T]] = OFormat(
      Reads.nullable(path)(r),
      Writes.at(path)(Writes.optionWithNull[T](w))
    )

    /**
     * 包含转换规则的JsonReads, 用于兼容历史数据
     *
     *  - Json读
     *   1. 尝试 `path` 下读取新类型 `T`, 允许 `path` 不存在
     *   1. 如果失败则尝试在 `path1` 下读取 `T1`, 并用 `convert` 将其转换为`T`
     *   1. 如果失败则返回 `JsError`
     *
     * @param path1   备用路径
     * @param convert 转换函数
     * @param fmtT    新类型的JsonFormat
     * @param fmtT1   旧类型的JsonFormat
     * @tparam T  新类型
     * @tparam T1 旧类型
     *
     * @return 包含上述转换规则的JsonReads
     */
    def readNullable2[T, T1](path1: JsPath, convert: T1 => T)(
        implicit fmtT: Format[T],
        fmtT1: Format[T1]
    ): Reads[Option[T]] =
      Reads.nullable(path)(fmtT).flatMap[Option[T]] {
        case None    => Reads.nullable(path1)(fmtT1).map(_.map(convert))
        case Some(t) => Reads.at(path)(Reads.pure(Some(t)))
      }

    /**
     * 包含转换规则的JsonFormat, 用于兼容历史数据
     *
     *  - Json读 [[readNullable2]]
     *  - Json写 对于None写入 `key : null`
     *
     * @param path1   备用路径
     * @param convert 转换函数
     * @param fmtT    新类型的JsonFormat
     * @param fmtT1   旧类型的JsonFormat
     * @tparam T  新类型
     * @tparam T1 旧类型
     *
     * @return 包含上述转换规则的JsonFormat
     */
    def formatNullable2[T, T1](path1: JsPath, convert: T1 => T)(
        implicit fmtT: Format[T],
        fmtT1: Format[T1]
    ): OFormat[Option[T]] = OFormat(
      readNullable2[T, T1](path1, convert),
      Writes.at(path)(Writes.optionWithNull[T](fmtT))
    )

    /**
     * 包含转换规则的JsonFormat, 用于兼容历史数据
     *
     *  - Json读 [[readNullable2]]
     *  - Json写 对于None不写入 `key`
     *
     * @param path1   备用路径
     * @param convert 转换函数
     * @param fmtT    新类型的JsonFormat
     * @param fmtT1   旧类型的JsonFormat
     * @tparam T  新类型
     * @tparam T1 旧类型
     *
     * @return 包含上述转换规则的JsonFormat
     */
    def formatNullable20[T, T1](path1: JsPath, convert: T1 => T)(
        implicit fmtT: Format[T],
        fmtT1: Format[T1]
    ): OFormat[Option[T]] = OFormat(
      readNullable2[T, T1](path1, convert),
      Writes.nullable(path)(fmtT)
    )

    /**
     * 包含转换规则的JsonReads, 用于兼容历史数据
     *
     *  - Json读
     *   1. 尝试 `path` 下读取新类型 `T`, `path` 必须存在
     *   1. 如果失败则尝试在当前路径下读取 `T1`, 并用 `convert` 将其转换为`T`
     *   1. 如果失败则返回 `JsError`
     *
     * @param convert 转换函数
     * @param fmtT    新类型的JsonFormat
     * @param fmtT1   旧类型的JsonFormat
     * @tparam T  新类型
     * @tparam T1 旧类型
     *
     * @return 包含上述转换规则的JsonReads
     */
    def readNullable3[T, T1](convert: T1 => T)(
        implicit fmtT: Reads[T],
        fmtT1: Reads[T1]
    ): Reads[Option[T]] =
      Reads
        .at(path)(Reads.optionWithNull(fmtT))
        .orElse(
          Reads.at(path)(Reads.optionWithNull(fmtT1)).map(_.map(convert))
        )

    /**
     * 包含转换规则的JsonFormat, 用于兼容历史数据
     *
     *  - Json读 [[readNullable3]]
     *  - Json写 对于None写入 `key : null`
     *
     * @param convert 转换函数
     * @param fmtT    新类型的JsonFormat
     * @param fmtT1   旧类型的JsonFormat
     * @tparam T  新类型
     * @tparam T1 旧类型
     *
     * @return 包含上述转换规则的JsonFormat
     */
    def formatNullable3[T, T1](convert: T1 => T)(
        implicit fmtT: Format[T],
        fmtT1: Format[T1]
    ): OFormat[Option[T]] = OFormat(
      readNullable3[T, T1](convert),
      Writes.at(path)(Writes.optionWithNull[T](fmtT))
    )
  }

  implicit def optionalFormat[T](implicit jsFmt: Format[T]): Format[Option[T]] =
    new Format[Option[T]] {
      override def reads(json: JsValue): JsResult[Option[T]] = json match {
        case JsNull => JsSuccess(None)
        case js     => jsFmt.reads(js).map(Some(_))
      }

      override def writes(o: Option[T]): JsValue = o match {
        case None    => JsNull
        case Some(t) => jsFmt.writes(t)
      }
    }
}
